//-------------------------------------------------------------------
//	Copyright (c) 2010  Zhirnov Andrey
//	This file is part of the "UXGEN" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#include "ImageSaver.h"

namespace Engine
{
namespace Loaders
{

#	pragma pack(push, 1)

	struct TBitmapFileHeader	//BITMAPFILEHEADER
	{
		uint16	bfType;
		uint32	bfSize;
		uint16	bfReserved1;
		uint16	bfReserved2;
		uint32	bfOffBits;
	};

	struct TBitmapInfoHeader	//BITMAPINFOHEADER
	{
		uint32	biSize;
		int32	biWidth;
		int32	biHeight;
		uint16	biPlanes;
		uint16	biBitCount;
		uint32	biCompression;
		uint32	biSizeImage;
		int32	biXPelsPerMeter;
		int32	biYPelsPerMeter;
		uint32	biClrUsed;
		uint32	biClrImportant; 
	};

#	pragma pack(pop)


	bool SaveTexture1D(IFilePtr pFile, const TImage1D &sImage);
	bool SaveTextureBuffer(IFilePtr pFile, const TImage1D &sImage);
	bool SaveTexture2D(IFilePtr pFile, const TImage2D &sImage);
	bool SaveCubeMap(IFilePtr pFile, const array<TImage2D> &aImages);
	bool SaveTexture2DArray(IFilePtr pFile, const array<TImage2D> &aImages);
	bool SaveCubeMapArray(IFilePtr pFile, const array<TImage2D> &aImages);
	bool SaveTexture3D(IFilePtr pFile, const TImage3D &sImage);


	bool SaveImage1D(IFilePtr pFile, const TImage1D &sImage, e_image::type eType)
	{
		CHECK_ARGUMENT( pFile != nullptr );

		bool	ret = true;

		if ( eType == e_image::TEXTURE_1D )		ret = SaveTexture1D( pFile, sImage );		else
		if ( eType == e_image::TEXTURE_BUFFER )	ret = SaveTextureBuffer( pFile, sImage );	else
			RETURN_ERROR( "not suitable texture type" );
		
		CHECK_ERROR( ret, "Can't save texture \"" << pFile->GetName() << '"' );
		return true;
	}


	bool SaveImage2D(IFilePtr pFile, const TImage2D &sImage, e_image::type eType)
	{
		CHECK_ARGUMENT( pFile != nullptr );
		
		bool	ret = true;

		if ( eType == e_image::TEXTURE_2D )	ret = SaveTexture2D( pFile, sImage );	else
			RETURN_ERROR( "not suitable texture type" );
		
		CHECK_ERROR( ret, "Can't save texture \"" << pFile->GetName() << '"' );
		return true;
	}


	bool SaveImage3D(IFilePtr pFile, const TImage3D &sImage, e_image::type eType)
	{
		CHECK_ARGUMENT( pFile != nullptr );
		
		bool	ret = true;

		if ( eType == e_image::TEXTURE_3D )	ret = SaveTexture3D( pFile, sImage );	else
			RETURN_ERROR( "not suitable texture type" );
		
		CHECK_ERROR( ret, "Can't save texture \"" << pFile->GetName() << '"' );
		return true;
	}


	bool SaveImageArray2D(IFilePtr pFile, const array<TImage2D> &aImages, e_image::type eType)
	{
		CHECK_ARGUMENT( pFile != nullptr );
		
		bool	ret = true;

		if ( eType == e_image::CUBE_MAP )			ret	= SaveCubeMap( pFile, aImages );		else
		if ( eType == e_image::TEXTURE_2D_ARRAY )	ret = SaveTexture2DArray( pFile, aImages );	else
		if ( eType == e_image::CUBEMAP_ARRAY )		ret = SaveCubeMapArray( pFile, aImages );	else
			RETURN_ERROR( "not suitable texture type" );
		
		CHECK_ERROR( ret, "Can't save texture \"" << pFile->GetName() << '"' );
		return true;
	}


	bool SaveSprite2D(IFilePtr pFile, const TSprite2D &sSprite)
	{
		CHECK_ARGUMENT( pFile != nullptr );

		TFileHeader			s_file_header;
		TSprite2DHeader		s_header;
		bool				b_write		 = true;
		uint				temp		 = 0;

		s_file_header.uHeaderSize	= sizeof(TSprite2DHeader);
		s_file_header.uUserDataSize	= 0;

		s_header.uRectCount			= sSprite.aTexcoords.Count();
		s_header.uIndicesCount		= sSprite.aFrames.Count();
		s_header.uAnimationsCount	= sSprite.aAnimation.Count();

		b_write &= pFile->WriteS( s_aSprFileHeader,				12 );
		b_write &= pFile->WriteS( &s_file_header,				sizeof(s_file_header) );
		b_write &= pFile->WriteS( &s_header,						sizeof(s_header) );
		b_write &= pFile->WriteS( sSprite.aTexcoords.ptr(),		sSprite.aTexcoords.Size() );
		b_write &= pFile->WriteS( sSprite.aFrames.ptr(),		sSprite.aFrames.Size() );

		for (uint i = 0; i < sSprite.aAnimation.Count(); ++i)
		{
			TSprAnimHeader	s_anim;
			uint			u_name_size;

			if ( sSprite.aAnimation[i].sName.Length() >= sizeof(s_anim.aName) )
				u_name_size = sizeof(s_anim.aName)-1;
			else
				u_name_size = sSprite.aAnimation[i].sName.Length();

			memcpy( s_anim.aName, sSprite.aAnimation[i].sName.cstr(), u_name_size+1 );
			s_anim.aName[u_name_size] = '\0';

			s_anim.uCount	= sSprite.aAnimation[i].uCount;
			s_anim.uOffset	= sSprite.aAnimation[i].uOffset;

			b_write &= pFile->WriteS( &s_anim, sizeof(s_anim) );
		}

		b_write &= SaveImage2D( pFile, sSprite.sImage, e_image::TEXTURE_2D );
		
		CHECK_ERROR( b_write, "Can't save sprite \"" << pFile->GetName() << '"' );
		return true;
	}


	bool SaveBitmap(IFilePtr pFile, const uvec2 &sSize, const binary_buffer_t &aData)
	{
		CHECK_ARGUMENT( pFile != nullptr );

		TBitmapFileHeader	s_file_header;
		TBitmapInfoHeader	s_bmp_header;
		bool				b_write	= true;
		
		s_file_header.bfType		= 0x4d42;
		s_file_header.bfSize		= 0;
		s_file_header.bfReserved1	= 0;
		s_file_header.bfReserved2	= 0;
		s_file_header.bfOffBits		= sizeof(TBitmapFileHeader) + sizeof(TBitmapInfoHeader);
		
		s_bmp_header.biSize			= sizeof(TBitmapInfoHeader);
		s_bmp_header.biWidth		= sSize.x;
		s_bmp_header.biHeight		= sSize.y;
		s_bmp_header.biPlanes		= 1;
		s_bmp_header.biBitCount		= 24;
		s_bmp_header.biCompression	= 0;	// BI_RGB
		s_bmp_header.biSizeImage	= (uint)aData.Size();
		s_bmp_header.biXPelsPerMeter = 0;
		s_bmp_header.biYPelsPerMeter = 0;
		s_bmp_header.biClrUsed		= 0;
		s_bmp_header.biClrImportant	= 0;

		b_write &= pFile->WriteS( &s_file_header,	sizeof(s_file_header) );
		b_write &= pFile->WriteS( &s_bmp_header,		sizeof(s_bmp_header) );
		b_write &= pFile->WriteS( aData.ptr(),		(uint)aData.Size() );
		
		CHECK_ERROR( b_write, "Can't save bitmap \"" << pFile->GetName() << '"' );
		return true;
	}


	bool SaveTexture1D(IFilePtr pFile, const TImage1D &sImage)
	{
		char				a_header[5] = "T1D\n";
		TFileHeader			s_file_header;
		TTexture1DHeader	s_header;
		bool				b_write = true;
		GLenum				a_formats[3] = {0};

		s_file_header.uHeaderSize	= sizeof(s_header);
		s_file_header.uUserDataSize	= 0;

		b_write &= Image::ConvertFormat( sImage.eFormat, sImage.bCompressed, a_formats[0], a_formats[1], a_formats[2] );

		TCheckConverter::Convert( s_header.eFormat[0], a_formats[0] );
		TCheckConverter::Convert( s_header.eFormat[1], a_formats[1] );
		TCheckConverter::Convert( s_header.eFormat[2], a_formats[2] );

		s_header.eSwizzle		= sImage.eSwizzle;
		s_header.uDataSize		= (uint)sImage.aData.Size();
		s_header.uLength		= sImage.uLength;
		s_header.uMipMapsCount	= (uint)sImage.aMipMaps.Count();
		s_header.bCompress		= sImage.bCompressed;

		b_write &= pFile->WriteS( s_aTexFileHeader,			12 );
		b_write &= pFile->WriteS( a_header,					4 );
		b_write &= pFile->WriteS( &s_file_header,			sizeof(s_file_header) );
		b_write &= pFile->WriteS( &s_header,					sizeof(s_header) );
		b_write &= pFile->WriteS( sImage.aMipMaps.ptr(),	(uint)sImage.aMipMaps.Size() );
		b_write &= pFile->WriteS( sImage.aData.ptr(),		(uint)sImage.aData.Size() );

		return b_write;
	}


	bool SaveTextureBuffer(IFilePtr pFile, const TImage1D &sImage)
	{
		char			a_header[5] = "TBO\n";
		TFileHeader		s_file_header;
		TTBOHeader		s_header;
		bool			b_write = true;
		GLenum			a_formats[3] = {0};

		s_file_header.uHeaderSize	= sizeof(s_header);
		s_file_header.uUserDataSize	= 0;
		
		b_write &= Image::ConvertFormat( sImage.eFormat, sImage.bCompressed, a_formats[0], a_formats[1], a_formats[2] );

		TCheckConverter::Convert( s_header.eFormat[0], a_formats[0] );
		TCheckConverter::Convert( s_header.eFormat[1], a_formats[1] );
		TCheckConverter::Convert( s_header.eFormat[2], a_formats[2] );

		s_header.uDataSize		= (uint)sImage.aData.Size();
		
		b_write &= pFile->WriteS( s_aTexFileHeader,		12 );
		b_write &= pFile->WriteS( a_header,				4 );
		b_write &= pFile->WriteS( &s_file_header,		sizeof(s_file_header) );
		b_write &= pFile->WriteS( &s_header,				sizeof(s_header) );
		b_write &= pFile->WriteS( sImage.aData.ptr(),	(uint)sImage.aData.Size() );

		return b_write;
	}


	bool SaveTexture2D(IFilePtr pFile, const TImage2D &sImage)
	{
		char				a_header[5] = "T2D\n";
		TFileHeader			s_file_header;
		TTexture2DHeader	s_header;
		bool				b_write = true;
		GLenum				a_formats[3] = {0};

		s_file_header.uHeaderSize	= sizeof(s_header);
		s_file_header.uUserDataSize	= 0;
		
		b_write &= Image::ConvertFormat( sImage.eFormat, sImage.bCompressed, a_formats[0], a_formats[1], a_formats[2] );

		TCheckConverter::Convert( s_header.eFormat[0], a_formats[0] );
		TCheckConverter::Convert( s_header.eFormat[1], a_formats[1] );
		TCheckConverter::Convert( s_header.eFormat[2], a_formats[2] );

		s_header.eSwizzle		= sImage.eSwizzle;
		s_header.uDataSize		= (uint)sImage.aData.Size();
		s_header.uWidth			= sImage.vTexDim.x;
		s_header.uHeight		= sImage.vTexDim.y;
		s_header.uMipMapsCount	= (uint)sImage.aMipMaps.Count();
		s_header.bCompress		= sImage.bCompressed;
		
		b_write &= pFile->WriteS( s_aTexFileHeader,			12 );
		b_write &= pFile->WriteS( a_header,					4 );
		b_write &= pFile->WriteS( &s_file_header,			sizeof(s_file_header) );
		b_write &= pFile->WriteS( &s_header,					sizeof(s_header) );
		b_write &= pFile->WriteS( sImage.aMipMaps.ptr(),	(uint)sImage.aMipMaps.Size() );
		b_write &= pFile->WriteS( sImage.aData.ptr(),		(uint)sImage.aData.Size() );

		return b_write;
	}


	bool SaveCubeMap(IFilePtr pFile, const array<TImage2D> &aImages)
	{
		CHECK_ARGUMENT( aImages.Count() == 6 );

		for (uint i = 1; i < aImages.Count(); ++i)
			CHECK_ARGUMENT( aImages[i].eFormat		== aImages[0].eFormat   &&
							All( aImages[i].vTexDim	== aImages[0].vTexDim ) &&
							aImages[i].aData.Size() == aImages[0].aData.Size() );

		char				a_header[5] = "CBM\n";
		TFileHeader			s_file_header;
		TTexture2DHeader	s_header;
		bool				b_write = true;
		GLenum				a_formats[3] = {0};

		s_file_header.uHeaderSize	= sizeof(s_header);
		s_file_header.uUserDataSize	= 0;
		
		b_write &= Image::ConvertFormat( aImages[0].eFormat, aImages[0].bCompressed, a_formats[0], a_formats[1], a_formats[2] );

		TCheckConverter::Convert( s_header.eFormat[0], a_formats[0] );
		TCheckConverter::Convert( s_header.eFormat[1], a_formats[1] );
		TCheckConverter::Convert( s_header.eFormat[2], a_formats[2] );

		s_header.eSwizzle		= aImages[0].eSwizzle;
		s_header.uDataSize		= (uint)aImages[0].aData.Size();
		s_header.uWidth			= aImages[0].vTexDim.x;
		s_header.uHeight		= aImages[0].vTexDim.y;
		s_header.uMipMapsCount	= (uint)aImages[0].aMipMaps.Count();
		s_header.bCompress		= aImages[0].bCompressed;
		
		b_write &= pFile->WriteS( s_aTexFileHeader, 12 );
		b_write &= pFile->WriteS( a_header,			4 );
		b_write &= pFile->WriteS( &s_file_header,	sizeof(s_file_header) );
		b_write &= pFile->WriteS( &s_header,			sizeof(s_header) );

		for (uint i = 0; i < aImages.Count(); ++i)
		{
			b_write &= pFile->WriteS( aImages[i].aMipMaps.ptr(), (uint)aImages[i].aMipMaps.Size() );
			b_write &= pFile->WriteS( aImages[i].aData.ptr(),	  (uint)aImages[i].aData.Size() );
		}

		return b_write;
	}


	bool SaveTexture2DArray(IFilePtr pFile, const array<TImage2D> &aImages)
	{
		CHECK_ARGUMENT( !aImages.Empty() );

		for (uint i = 1; i < aImages.Count(); ++i)
			CHECK_ARGUMENT( aImages[i].eFormat		== aImages[0].eFormat   &&
							All( aImages[i].vTexDim	== aImages[0].vTexDim ) &&
							aImages[i].aData.Size() == aImages[0].aData.Size() );

		char				a_header[5] = "T2A\n";
		TFileHeader			s_file_header;
		TTexture2DHeader	s_header;
		bool				b_write = true;
		uint				u_temp	= (uint)aImages.Count();
		GLenum				a_formats[3] = {0};

		s_file_header.uHeaderSize	= sizeof(s_header) + sizeof(u_temp);
		s_file_header.uUserDataSize	= 0;
		
		b_write &= pFile->WriteS( s_aTexFileHeader, 12 );
		b_write &= pFile->WriteS( a_header,			4 );
		b_write &= pFile->WriteS( &s_file_header,	sizeof(s_file_header) );
		b_write &= pFile->WriteS( &u_temp,			sizeof(u_temp) );
		
		b_write &= Image::ConvertFormat( aImages[0].eFormat, aImages[0].bCompressed, a_formats[0], a_formats[1], a_formats[2] );

		TCheckConverter::Convert( s_header.eFormat[0], a_formats[0] );
		TCheckConverter::Convert( s_header.eFormat[1], a_formats[1] );
		TCheckConverter::Convert( s_header.eFormat[2], a_formats[2] );

		s_header.eSwizzle		= aImages[0].eSwizzle;
		s_header.uDataSize		= (uint)aImages[0].aData.Size();
		s_header.uWidth			= aImages[0].vTexDim.x;
		s_header.uHeight		= aImages[0].vTexDim.y;
		s_header.uMipMapsCount	= (uint)aImages[0].aMipMaps.Count();
		s_header.bCompress		= aImages[0].bCompressed;

		b_write &= pFile->WriteS( &s_header,	 sizeof(s_header) );

		for (uint i = 0; i < aImages.Count(); ++i)
		{
			b_write &= pFile->WriteS( aImages[i].aMipMaps.ptr(), (uint)aImages[i].aMipMaps.Size() );
			b_write &= pFile->WriteS( aImages[i].aData.ptr(),	  (uint)aImages[i].aData.Size() );
		}

		return b_write;
	}


	bool SaveCubeMapArray(IFilePtr pFile, const array<TImage2D> &aImages)
	{
		CHECK_ARGUMENT( !aImages.Empty() && aImages.Count() % 6 == 0 );

		for (uint i = 1; i < aImages.Count(); ++i)
			CHECK_ARGUMENT( aImages[i].eFormat		== aImages[0].eFormat   &&
							All( aImages[i].vTexDim	== aImages[0].vTexDim ) &&
							aImages[i].aData.Size() == aImages[0].aData.Size() );

		char				a_header[5] = "CMA\n";
		TFileHeader			s_file_header;
		TTexture2DHeader	s_header;
		bool				b_write = true;
		uint				u_temp	= (uint)( aImages.Count()/6 );
		GLenum				a_formats[3] = {0};

		s_file_header.uHeaderSize	= sizeof(s_header) + sizeof(u_temp);
		s_file_header.uUserDataSize	= 0;
		
		b_write &= pFile->WriteS( s_aTexFileHeader, 12 );
		b_write &= pFile->WriteS( a_header,			4 );
		b_write &= pFile->WriteS( &s_file_header,	sizeof(s_file_header) );
		b_write &= pFile->WriteS( &u_temp,			sizeof(u_temp) );
		
		b_write &= Image::ConvertFormat( aImages[0].eFormat, aImages[0].bCompressed, a_formats[0], a_formats[1], a_formats[2] );

		TCheckConverter::Convert( s_header.eFormat[0], a_formats[0] );
		TCheckConverter::Convert( s_header.eFormat[1], a_formats[1] );
		TCheckConverter::Convert( s_header.eFormat[2], a_formats[2] );

		s_header.eSwizzle		= aImages[0].eSwizzle;
		s_header.uDataSize		= (uint)aImages[0].aData.Size();
		s_header.uWidth			= aImages[0].vTexDim.x;
		s_header.uHeight		= aImages[0].vTexDim.y;
		s_header.uMipMapsCount	= (uint)aImages[0].aMipMaps.Count();
		s_header.bCompress		= aImages[0].bCompressed;

		b_write &= pFile->WriteS( &s_header,	 sizeof(s_header) );

		for (uint i = 0; i < aImages.Count(); ++i)
		{
			b_write &= pFile->WriteS( aImages[i].aMipMaps.ptr(), (uint)aImages[i].aMipMaps.Size() );
			b_write &= pFile->WriteS( aImages[i].aData.ptr(),	  (uint)aImages[i].aData.Size() );
		}

		return b_write;
	}


	bool SaveTexture3D(IFilePtr pFile, const TImage3D &sImage)
	{
		char				a_header[5] = "T3D\n";
		TFileHeader			s_file_header;
		TTexture3DHeader	s_header;
		bool				b_write = true;
		GLenum				a_formats[3] = {0};

		s_file_header.uHeaderSize	= sizeof(s_header);
		s_file_header.uUserDataSize	= 0;
		
		b_write &= Image::ConvertFormat( sImage.eFormat, sImage.bCompressed, a_formats[0], a_formats[1], a_formats[2] );

		TCheckConverter::Convert( s_header.eFormat[0], a_formats[0] );
		TCheckConverter::Convert( s_header.eFormat[1], a_formats[1] );
		TCheckConverter::Convert( s_header.eFormat[2], a_formats[2] );

		s_header.eSwizzle		= sImage.eSwizzle;
		s_header.uDataSize		= (uint)sImage.aData.Size();
		s_header.uWidth			= sImage.vTexDim.x;
		s_header.uHeight		= sImage.vTexDim.y;
		s_header.uDepth			= sImage.vTexDim.z;
		s_header.uMipMapsCount	= (uint)sImage.aMipMaps.Count();
		s_header.bCompress		= sImage.bCompressed;
		
		b_write &= pFile->WriteS( s_aTexFileHeader,			12 );
		b_write &= pFile->WriteS( a_header,					4 );
		b_write &= pFile->WriteS( &s_file_header,			sizeof(s_file_header) );
		b_write &= pFile->WriteS( &s_header,					sizeof(s_header) );
		b_write &= pFile->WriteS( sImage.aMipMaps.ptr(),	(uint)sImage.aMipMaps.Size() );
		b_write &= pFile->WriteS( sImage.aData.ptr(),		(uint)sImage.aData.Size() );

		return b_write;
	}

//-------------------------------------------------------------------

}	// Loaders
}	// Engine